use std::{mem, ptr};

use jni::{
    objects::{JByteArray, JClass, JString, ReleaseMode},
    sys::{jbyteArray, jint, jlong},
    JNIEnv,
};

use crate::{
    conn::serial_port::SerialConn,
    error::{AppErr, ErrWrap},
    utils::{ drop_box, get_raw_ref, get_rt, java::CLASS_IO_EXCEPTION},
};

fn open_impl(env: &mut JNIEnv, name: &JString, baud_rate: jint) -> Result<jlong, AppErr> {
    let name = unsafe { env.get_string_unchecked(name)? };
    let name = name.to_str().wrap()?;
    let conn = get_rt().block_on(async { SerialConn::new(name, baud_rate as u32) })?;
    let conn = Box::new(conn);
    
    Ok(Box::into_raw(conn) as jlong)
}

#[no_mangle]
pub extern "system" fn Java_rust_1serial_NativeKt_nativeOpen(
    mut env: JNIEnv,
    _clz: JClass,
    name: JString,
    baud_rate: jint,
) -> jlong {
    match open_impl(&mut env, &name, baud_rate) {
        Ok(fd) => fd,
        Err(e) => {
            e.throw_exception(&mut env, CLASS_IO_EXCEPTION);
            -1
        }
    }
}

fn write_impl(env: &mut JNIEnv, fd: jlong, buf: &JByteArray) -> Result<(), AppErr> {

    let buf = unsafe {
        env.get_array_elements(buf, ReleaseMode::NoCopyBack)
    }?;
    let buf: &[i8] = &buf;
    let buf: &[u8] = unsafe {
        mem::transmute(buf)
    };

    let w: &SerialConn = get_raw_ref(fd);
    get_rt().block_on( w.write(buf) )?;
    Ok(())
}

#[no_mangle]
pub extern "system" fn Java_rust_1serial_NativeKt_nativeWrite(
    mut env: JNIEnv,
    _clz: JClass,
    fd: jlong,
    buf: JByteArray
) {
    if let Err(e) = write_impl(&mut env, fd, &buf) {
        e.throw_exception(&mut env, CLASS_IO_EXCEPTION);
    }
}

fn read_impl<'a>(env: &'a mut JNIEnv, fd: jlong) -> Result<JByteArray<'a>, AppErr> {

    let r: & SerialConn = get_raw_ref(fd);
    let buf = get_rt().block_on(r.read())?;
    let buf = env.byte_array_from_slice(&buf)?;
    Ok(buf)
}

#[no_mangle]
pub extern "system" fn Java_rust_1serial_NativeKt_nativeRead<'a>(
    mut env: JNIEnv<'a>,
    _clz: JClass,
    fd: jlong
) -> jbyteArray {

    match read_impl(&mut env, fd) {
        Ok(v) => return v.as_raw(),

        Err(e) => {
            e.throw_exception(&mut env, CLASS_IO_EXCEPTION);
        }
    };

    ptr::null_mut()
}

#[no_mangle]
pub extern "system" fn Java_rust_1serial_NativeKt_nativeClose(
    mut _env: JNIEnv,
    _clz: JClass,
    fd: jlong
) {
    let conn: &SerialConn = get_raw_ref(fd);
    conn.close();
}

#[no_mangle]
pub extern "system" fn Java_rust_1serial_NativeKt_nativeDelete(
    mut _env: JNIEnv,
    _clz: JClass,
    fd: jlong
) {
    drop_box::<SerialConn>(fd);
}
